通过基础篇:后台同步可知,我们可通过后台同步机制来解决传统 Web 应用所存在的以下问题:
- 页面发起的请求会随着页面的关闭而终止。
- 在离线状态下,很难将用户的网络请求缓存起来,并在网络恢复正常后再次进行请求。
- 从而为用户提供了恶劣网络环境下,无感知事务处理的能力。在该章中我们已对底层 API 的使用进行了详细介绍,故本章不再重述相关细节,而是直接对 Workbox 相关接口进行介绍说明。
# 基本使用
Workbox 使用
workbox.backgroundSync.Plugin完成后台同步的注册,比如:
workbox.routing.registerRoute(
'/articles',
new workbox.strategies.NetworkOnly({
plugins: [
new workbox.backgroundSync.Plugin('createArticle')
]
}),
'POST'
);
示例中,当
POST /articles请求失败时,Workbox 会自动将该请求添加到后台同步队列 createArticle 中,并在 sync 事件中自动进行重试。其中workbox.backgroundSync.Plugin的参数按照顺序依次为:
name:队列名称,该参数的值必须全局唯一,否则将抛出duplicate-queue-name异常。options: 配置信息,相关属性为maxRetentionTime:请求的最大有效时长(单位为分钟,默认值为七天),如果请求超过所指定的期限,将从队列中移除。onSync:sync触发时的回调函数,该回调函数的参数为含有 queue(类型为workbox.backgroundSync.Queue实例)属性的对象,如不指定将调用workbox.backgroundSync.Queue实例的replayRequests方法。若指定该属性的值,如果回调函数处理失败,需要抛出异常,以便浏览器后续继续进行尝试。
由于
workbox.backgroundSync.Plugin内部使用了workbox.backgroundSync.Queue来管理同步请求队列,因此我们可以使用它来自行控制请求被加入队列的时机,比如
const queue = new workbox.backgroundSync.Queue('createArticle');
self.addEventListener('fetch', event => {
event.waitUntil(
fetch(event.request.clone).catch(() => {
return queue.pushRequest({ request: event.request });
})
);
});
示例中,我们首先定义了
workbox.backgroundSync.Queue实例queue,然后在 fetch 事件中,如果请求出现异常,则调用 queue 的 pushRequest 方法将相关请求添加到同步队列中,之后 sync 事件触发后将自动重试队列中的请求。由于workbox.backgroundSync.Queue的参数与workbox.backgroundSync.Plugin一致,故不再重述。
# 同步事件注册
为了启用后台同步,我们在页面中注册了同步事件,主要代码如下:
window.addEventListener('load', function() {
if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.register('./sw.js').then(function(registration) {
document.getElementById('submit').addEventListener('click', function() {
ui.submit(function(name) {
db.addTodo(name).then(function() {
registration.sync.register('add-todo');
});
});
});
});
} else {
document.getElementById('submit').addEventListener('click', function() {
ui.submit(function(name) {
network.addTodos([{ name: name }]).then(function(todos) {
ui.render(todos);
});
});
});
}
});
上例主要存在以下问题:
- 为了兼容不支持后台同步的浏览器,我们必须要对每一个网络请求做兼容性处理;
- 由于 Service Worker 线程无法直接访问 DOM,故需先将请求参数缓存在 IndexedDB 中,以便 Service Worker 在 sync 事件中能够成功构建相关请求。这便要求在页面主线程与 Service Worker 线程中所使用 IndexedDB 数据结构必须保持一致,如果一方更新了结构,而另一方尚未更新,则将造成意想不到的问题;
- 也由于后台同步只有在网络发生异常的情况下才能体现出其价值,但对处于稳定网络环境下的用户来说,在真正发起请求之前还要等待:缓存请求参数、注册同步事件、触发 sync 事件等一系列事件,这些不必要的开销累积起来很可能会严重影响用户体验;
- 最后,也是最重要的一点,PWA 所提倡的是以渐进、尽量少入侵的方式来为已有的 Web 应用添加离线处理等各种能力,如在页面中注册同步事件,将要大面积修改代码,明显违背了 PWA 的初衷。
基于以上原因,在 Workbox 中,无论是使用
workbox.backgroundSync.Plugin,还是workbox.backgroundSync.Queue,我们都无需对页面代码进行任何修改,因此上例代码可简化为:
window.addEventListener('load', function() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js');
}
document.getElementById('submit').addEventListener('click', function() {
ui.submit(function(name) {
network.addTodos([{ name: name }]).then(function(todos) {
ui.render(todos);
});
});
});
});
这是因为 Workbox 在将请求加入同步队列时自动完成了同步事件的注册,如果我们使用
workbox.backgroundSync.Plugin中仅在请求失败时将其加入同步队列的策略,这即能保证稳定网络下的高效率,又能解决网络中断又恢复后的自动重试问题。因此,无论是否使用 Workbox 的后台同步接口,尽量在 Service Worker 线程中,并且在请求异常时注册同步事件。
# 总结
本章我们首先对 Workbox 后台同步接口进行了介绍,然后简单讨论了同步事件注册的时机问题,通过 Workbox,我们无需修改页面逻辑便可为已有 Web 应用添加离线事务处理的能力,这也进一步体现了 PWA 中的渐进式思想。下一章,我们将对 Workbox 的插件进行讨论。